// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package template

import (
	"fmt"
	"text/template/parse"
)

// 错误描述了模板转义过程中遇到的问题。
type Error struct {
	// 错误代码描述了错误的类型。
	ErrorCode ErrorCode
	// 节点是导致问题的节点（如果已知）。
	// 如果不是nil，它将覆盖Name和Line。
	Node parse.Node
	// Name是遇到错误的模板的名称。
	Name string
	// Line是模板源中错误的行号或0。
	Line int
	// 描述是对问题的可读描述。
	Description string
}

// ErrorCode是一种错误的代码。
type ErrorCode int

// 我们为转义模板时出现的每个错误定义代码，但
// 转义模板也可能在运行时失败。
// 
// 输出：“ZgotmplZ”
// 示例：
// /<img src=“{X}”>
// 其中{X}的计算结果为`javascript:…`
// 讨论：
// “ZgotmplZ”是一个特殊值，表示不安全内容在运行时到达了
// CSS或URL上下文。该示例的输出将是
// <img src=“#ZgotmplZ”>
// 如果数据来自可信来源，请使用内容类型将其从过滤：URL（`javascript:…`）中免除。
const (
	// OK表示没有错误。
	OK ErrorCode = iota

	// errrambigcontext:“……出现在一个不明确的URL中”
	// Example:
	// <a href=“
	// {if.C}
	// /path/
	// {else}
	// /search？q=
	// /{end}
	// /{{.X}}处于不明确的URL上下文中，因为根据{{.C}，
	// 它可能是URL后缀或查询参数。
	// 将{X}移动到条件中可以消除歧义：
	// /<a href=“{{if.C}}/path/{{if.C}}}}{{else}/search？q={X}”>，
	ErrAmbigContext

	// “…在未加引号的attr”中，“…在属性名中”
	// 示例：
	// <a href=/search？q=foo>
	// /<href=foo>
	// /<form na<e=…>
	// <option selected<
	// 讨论：
	// 这通常是由于HTML元素中的输入错误造成的，但是一些符文
	// 在标记名、属性名和未加引号的属性
	// 值中被禁止，因为它们会引起解析器的歧义。
	// 引用所有属性是最好的策略。
	ErrBadHTML

	// errbranchen:“{if}}分支在不同的上下文中结束”
	// 示例：
	// /{if.C}}<a href=“{{end}}{
	// 讨论：
	// 包html/模板通过
	// /{if}，{range}或{}为了逃离下面的管道。
	// 由于{X}}可能是HTML文本节点、
	// 或HTML属性中的URL前缀，因此该示例不明确。{X}的上下文是
	// 用于找出如何转义它，但该上下文取决于
	// 未静态已知的{C}的运行时值。
	// 
	// 问题通常是缺少引号或角度
	// 括号，或者可以通过重构将这两个上下文
	// 放入if、range或with的不同分支来避免。如果问题
	// 位于不应为空的集合上的{range}内，
	// 添加一个伪{else}可能会有所帮助。
	ErrBranchEnd

	// ErrEndContext:“……在非文本上下文中结束：…”
	// 示例：
	// /<div 
	// /<div title=“no close quote>
	// /<script>f（）
	// 讨论：
	// 执行的模板应生成HTML的文档片段。
	// 结束时没有结束标记的模板将触发此错误。
	// 不应在HTML上下文中使用的模板，或
	// 产生不完整片段的模板，不应直接执行。
	// 
	// /{{define“main”}}<script>{template“helper”}</script>{{end}}}
	// /{define“helper”}文档。写入（“<div title=“”）{end}
	// 
	// “helper”无法生成有效的文档片段，因此不应直接执行
	// 。
	ErrEndContext

	// ErrNoSuchTemplate:“没有这样的模板…”
	// 示例：
	// /{{define“main”}}<div{{template“attrs”}>{{end}}}
	// /{define“attrs”}href=“{end}URL}}}{
	// 讨论：
	// Package html/template查看模板调用以计算
	// 在这里，当从“main”调用
	// 时，“attrs”中的{.URL}必须被视为URL，但如果在解析“main”时没有定义“attrs”，则会出现此错误。
	ErrNoSuchTemplate

	// ErrOutputContext:“无法计算模板的输出上下文…”
	// 示例：
	// /{{define“t”}{{if.t}}{{template“t”.t}}}{{{template“t”{end}}{{H}}，{end}}
	// 讨论：
	// 递归模板的结束位置与开始位置不同，因此无法计算可靠的输出上下文。
	// 在指定模板中查找拼写错误。
	// 如果不应在命名的开始上下文中调用模板，
	// 请在意外的上下文中查找对该模板的调用。
	// 可能会将递归模板重构为非递归模板。
	ErrOutputContext

	// ErrPartialCharset:“未完成的JS regexp字符集在…”
	// 示例：
	// /<script>var pattern=/foo[{.Chars}]/</script>
	// 讨论：
	// 包html/模板不支持插入到常规
	// 表达式文字字符集。
	ErrPartialCharset

	// errpartialscape:“未完成的转义序列在…”
	// 示例：
	// /<script>alert（\{.X}”）</script>
	// 讨论：
	// 包html/模板不支持
	// 反斜杠后面的操作。
	// 这通常是一个错误，有更好的解决方案；对于
	// example 
	// <script>alert（“{X}”）</script>
	// /应该可以工作，如果{X}是一个部分转义序列，比如
	// “xA0”，则将整个序列标记为安全内容：JSStr（`\xA0`）
	ErrPartialEscape

	// ErrRangeLoopReentry:“…”
	// 示例：
	// <script>var x=[{{range.}]{{.}},{end}]</script>
	// 讨论：
	// 如果通过一个范围的迭代会导致它在一个与之前的过程不同的上下文中结束，那么没有单一上下文。
	// 示例中缺少一个引号，因此不清楚
	// 是否{.}是指在JS字符串或JS值
	// 上下文中。第二次迭代将生成类似于
	// 
	// /<script>var x=['firstValue，'secondValue]</script>
	ErrRangeLoopReentry

	// /ErrSlashAmbig:'/'可以开始除法或regexp。
	// 示例：
	// /<script>
	// /{{if.C}}var x=1{{end}}
	// /-{{N}}/i.test（x）？doThis:doThat（）；
	// </script>
	// 讨论：
	// 上面的例子可能会产生`var x=1/-2/i.test..`
	// 其中第一个“/”是一个数学除法运算符，或者它
	// 可以生成“/-2/i.test”，其中第一个“/”开始一个
	// regexp literal。
	// 在分支中查找缺少的分号，并可能添加
	// 括号，以明确您想要的解释。
	ErrSlashAmbig

	// ErrPredefinedEscaper:“预定义的escaper…在模板中不允许”
	// Example:
	// <div class={{.|html}>Hello<div>
	// Discussion:
	// Package html/template已经在上下文中转义所有管道到
	// 。使用预定义的转义符“html”或“urlquery”手动转义
	// 管道输出是不必要的，可能会影响Go 1.8及更早版本中转义的
	// 管道输出的正确性或安全性。
	// 
	// 在大多数情况下，例如给定的示例，这个错误可以通过
	// 简单地从管道中删除预定义的转义器，并让
	// 上下文自动转义器处理管道的转义来解决。另一方面，
	// /<a href=“{.X}”>链接</a>
	// 
	// 为了便于迁移到1.9及更高版本，将继续允许“html”和“urlquery”作为管道中的最后一个命令。但是，如果
	// 管道出现在一个不带引号的属性值上下文中，“html”是不允许的。避免在新模板中完全使用“html”和“urlquery”。
	ErrPredefinedEscaper
)

func (e *Error) Error() string {
	switch {
	case e.Node != nil:
		loc, _ := (*parse.Tree)(nil).ErrorContext(e.Node)
		return fmt.Sprintf("html/template:%s: %s", loc, e.Description)
	case e.Line != 0:
		return fmt.Sprintf("html/template:%s:%d: %s", e.Name, e.Line, e.Description)
	case e.Name != "":
		return fmt.Sprintf("html/template:%s: %s", e.Name, e.Description)
	}
	return "html/template: " + e.Description
}

// errorf在给定格式字符串f和args的情况下创建错误。
// 模板名称仍需提供。
func errorf(k ErrorCode, node parse.Node, line int, f string, args ...any) *Error {
	return &Error{k, node, "", line, fmt.Sprintf(f, args...)}
}
